/*
 * JBoss, Home of Professional Open Source
 * Copyright 2005, JBoss Inc., and individual contributors as indicated
 * by the @authors tag. See the copyright.txt in the distribution for a
 * full listing of individual contributors.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
package org.jbpm.pvm.internal.task;

import java.io.Serializable;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;

import org.jbpm.api.Execution;
import org.jbpm.api.JbpmException;
import org.jbpm.api.model.Event;
import org.jbpm.api.task.Assignable;
import org.jbpm.api.task.Participation;
import org.jbpm.api.task.Swimlane;
import org.jbpm.api.task.Task;
import org.jbpm.pvm.internal.client.ClientExecution;
import org.jbpm.pvm.internal.env.Environment;
import org.jbpm.pvm.internal.history.HistoryEvent;
import org.jbpm.pvm.internal.history.events.TaskComplete;
import org.jbpm.pvm.internal.history.events.TaskDelete;
import org.jbpm.pvm.internal.model.ExecutionImpl;
import org.jbpm.pvm.internal.model.ProcessDefinitionImpl;
import org.jbpm.pvm.internal.model.ScopeInstanceImpl;
import org.jbpm.pvm.internal.session.DbSession;
import org.jbpm.pvm.internal.util.EqualsUtil;
import org.jbpm.pvm.internal.util.Priority;

/**
 * is one task instance that can be assigned to an actor (read: put in someone's task list) and that
 * can trigger the continuation of execution of the token upon completion.
 */
public class TaskImpl extends ScopeInstanceImpl implements Serializable, OpenTask, Assignable {

  private static final long serialVersionUID = 1L;

  // private static Log log = Log.getLog(TaskImpl.class.getName());

  protected boolean isNew;
  protected String name;
  protected String description;

  protected String assignee;
  protected Set<ParticipationImpl> participations = new HashSet<ParticipationImpl>();

  protected String formResourceName;
  protected Date createTime;
  protected Date duedate;
  protected Integer progress;
  protected boolean isSignalling;

  protected int priority = Priority.NORMAL;

  protected String state = Task.STATE_OPEN;

  protected String taskDefinitionName;
  protected TaskDefinitionImpl taskDefinition;

  protected ExecutionImpl execution;
  protected ExecutionImpl processInstance;
  
  // local storage of the execution id such that it
  // can be lazily loaded when not needed.
  protected String executionId;
  
  protected String activityName;
  
  protected SwimlaneImpl swimlane;

  protected TaskImpl superTask;
  protected Set<TaskImpl> subTasks;

  protected Long executionDbid;
  protected Long superTaskDbid;

  public TaskImpl() {
  }

  // parent for variable lookup /////////////////////////////////////////////// 

  public ScopeInstanceImpl getParentVariableScope() {
    return execution;
  }
  
  public TaskImpl getTask() {
    return this;
  }

  // assignment ///////////////////////////////////////////////////////////////

  public void take(String userId) {
    if (assignee != null) {
      throw new JbpmException("task already taken by " + this.assignee);
    }
    setAssignee(userId, true);
  }

  public void setAssignee(String userId) {
    setAssignee(userId, false);
  }

  public void setAssignee(String assignee, boolean propagateToSwimlane) {
    this.assignee = assignee;
    if (propagateToSwimlane) {
      propagateAssigneeToSwimlane();
    }
    if (execution != null) {
      execution.fire(Event.ASSIGN, execution.getActivity());
    }
  }

  protected void propagateAssigneeToSwimlane() {
    if (swimlane != null) {
      swimlane.setAssignee(assignee);
    }
  }

  // participations /////////////////////////////////////////////////////////////

  // TODO: Why does it return the impl. not the interface?
  public Set<ParticipationImpl> getParticipations() {
    return participations;
  }

  public Set<ParticipationImpl> getAllParticipants() {
    Set<ParticipationImpl> allRoles = new HashSet<ParticipationImpl>();
    if (!participations.isEmpty()) {
      allRoles = new HashSet<ParticipationImpl>(participations);
    }
    if (swimlane != null) {
      allRoles.addAll((Set) swimlane.getParticipations());
    }
    return allRoles;
  }

  public void addCandidateGroup(String groupId) {
    addParticipation(null, groupId, Participation.CANDIDATE);
  }

  public void addCandidateUser(String userId) {
    addParticipation(userId, null, Participation.CANDIDATE);
  }

  public Participation addParticipation(String userId, String groupId, String type) {
    return addParticipant(new ParticipationImpl(userId, groupId, type));
  }

  private Participation addParticipant(ParticipationImpl participation) {
    participation.setTask(this);
    participations.add(participation);
    return participation;
  }

  public void removeParticipant(ParticipationImpl participation) {
    if (participation == null) {
      throw new JbpmException("participant is null");
    }
    if (participations.remove(participation)) {
      participation.setTask(null);
    }
  }

  // completion ///////////////////////////////////////////////////////////////

  public void complete() {
    complete(Task.STATE_COMPLETED);
  }

  public void complete(String outcome) {
    historyTaskComplete(outcome);
    
    if (isSignalling()) {
      ClientExecution execution = (ClientExecution) getExecution();
      execution.signal(outcome);
    }
    
    if (superTask != null) {
      superTask.subTaskComplete(this, outcome);
    }
  }

  protected void subTaskComplete(TaskImpl subTask, String outcome) {
  }

  public void delete(String reason) {
    historyTaskDelete(reason);
  }

  // state ////////////////////////////////////////////////////////////////////

  public boolean isCompleted() {
    if (Task.STATE_COMPLETED.equals(state)) {
      return true;
    }
    if ((Task.STATE_OPEN.equals(state)) || (Task.STATE_SUSPENDED.equals(state))) {
      return false;
    }
    return true;
  }

  // subtasks /////////////////////////////////////////////////////////////////

  public Set<Task> getSubTasks() {
    if (subTasks == null) {
      return Collections.emptySet();
    }
    return (Set) subTasks;
  }

  public TaskImpl createSubTask() {
    DbSession dbSession = Environment.getFromCurrent(DbSession.class);
    TaskImpl subTask = (TaskImpl) dbSession.createTask();
    if (subTasks == null) {
      subTasks = new HashSet<TaskImpl>();
    }
    addSubTask(subTask);
    return subTask;
  }

  public TaskImpl createSubTask(String name) {
    // TODO look up the task definition in the current task's 
    // subtask definitions and in the process's task definitions
    TaskImpl subtask = createSubTask();
    subtask.setName(name);
    return subtask;
  }

  public TaskImpl addSubTask(TaskImpl subtask) {
    if (subTasks == null) {
      subTasks = new HashSet<TaskImpl>();
    }
    subtask.setSuperTask(this);
    subTasks.add(subtask);
    return subtask;
  }

  public void removeSubTask(Task subtask) {
    if (subtask == null) {
      throw new JbpmException("subtask is null");
    }
    if ((subTasks != null) && (subTasks.remove(subtask))) {
      ((TaskImpl) subtask).setSuperTask(null);
    }
  }

  // equals ///////////////////////////////////////////////////////////////////
  // hack to support comparing hibernate proxies against the real objects
  // since this always falls back to ==, we don't need to overwrite the hashcode
  public boolean equals(Object o) {
    return EqualsUtil.equals(this, o);
  }

  public String toString() {
    return "Task(" + name + ")";
  }

  public String getLifeCycleResource() {
    // the default lifecycle can be overridden in subclasses
    return "jbpm.task.lifecycle.xml";
  }

  // modified getters and setters /////////////////////////////////////////////
  public void setProgress(Integer progress) {
    if ((progress < 0) || (progress > 100)) {
      throw new JbpmException(
          "task progress is a percentage (integer) and must be expressed between 0 and 100");
    }
    this.progress = progress;
  }

  public void cancelExecution(String signal) {
    if (execution != null) {
      execution.end("cancel");
    }
  }

  public void historyTaskDelete(String reason) {
    if (execution != null) {
      HistoryEvent.fire(new TaskDelete(this, reason), execution);
    }
  }

  public void historyTaskComplete(String outcome) {
    if (execution != null) {
      HistoryEvent.fire(new TaskComplete(outcome), execution);
    }
  }

  public void signalExecution(String signalName) {
    if (execution != null) {
      execution.signal(signalName);
    }
  }

  // special getters and setters //////////////////////////////////////////////

  public TaskDefinitionImpl getTaskDefinition() {
    if ((taskDefinition == null) && (taskDefinitionName != null) && (execution != null)) {
      ProcessDefinitionImpl processDefinition = execution.getProcessDefinition();
      taskDefinition = processDefinition.getTaskDefinition(taskDefinitionName);
    }

    return taskDefinition;
  }

  // customized getters and setters //////////////////////////////////////////
  
  public String getId() {
    return Long.toString(dbid);
  }

  public void setTaskDefinition(TaskDefinitionImpl taskDefinition) {
    this.taskDefinition = taskDefinition;
    this.taskDefinitionName = taskDefinition.getName();
  }

  public void setExecution(ExecutionImpl execution) {
    this.execution = execution;
    this.executionId = execution.getId();
    this.activityName = execution.getActivityName();
  }

  // getters and setters //////////////////////////////////////////////////////

  public String getName() {
    return name;
  }

  public void setName(String name) {
    this.name = name;
  }

  public String getDescription() {
    return description;
  }

  public void setDescription(String description) {
    this.description = description;
  }

  public int getPriority() {
    return priority;
  }

  public void setPriority(int priority) {
    this.priority = priority;
  }

  public Date getCreateTime() {
    return createTime;
  }

  public void setCreateTime(Date create) {
    this.createTime = create;
  }

  public Date getDuedate() {
    return duedate;
  }

  public void setDuedate(Date duedate) {
    this.duedate = duedate;
  }

  public ExecutionImpl getExecution() {
    return execution;
  }

  public void setExecution(Execution execution) {
    this.execution = (ExecutionImpl) execution;
  }

  public String getState() {
    return state;
  }

  public String getAssignee() {
    return assignee;
  }

  public Swimlane getSwimlane() {
    return swimlane;
  }

  public void setSwimlane(SwimlaneImpl swimlane) {
    this.swimlane = swimlane;
  }

  public TaskImpl getSuperTask() {
    return superTask;
  }

  public void setSuperTask(TaskImpl superTask) {
    this.superTask = superTask;
  }

  public Integer getProgress() {
    return progress;
  }

  public Long getExecutionDbid() {
    return executionDbid;
  }

  public void setExecutionDbid(Long executionDbid) {
    this.executionDbid = executionDbid;
  }

  public Long getSuperTaskDbid() {
    return superTaskDbid;
  }

  public void setSuperTaskDbid(Long parentTaskDbid) {
    this.superTaskDbid = parentTaskDbid;
  }

  public void setParticipations(Set<ParticipationImpl> participations) {
    this.participations = participations;
  }

  public void setState(String state) {
    this.state = state;
  }

  public String getExecutionId() {
    return executionId;
  }
  
  public String getActivityName() {
    return activityName;
  }

  public void setActivityName(String activityName) {
    this.activityName = activityName;
  }

  public void setSubTasks(Set<TaskImpl> subTasks) {
    this.subTasks = subTasks;
  }

  public ExecutionImpl getProcessInstance() {
    return processInstance;
  }

  public void setProcessInstance(ExecutionImpl processInstance) {
    this.processInstance = processInstance;
  }

  public boolean isSignalling() {
    return isSignalling;
  }

  public void setSignalling(boolean isSignalling) {
    this.isSignalling = isSignalling;
  }

  public String getFormResourceName() {
    return formResourceName;
  }

  public void setFormResourceName(String form) {
    this.formResourceName = form;
  }
  public boolean isNew() {
    return isNew;
  }
  public void setNew(boolean isNew) {
    this.isNew = isNew;
  }
}
